package cn.com.nes.site.interceptor;

import cn.com.nes.common.util.IPUtils;
import cn.com.nes.common.util.JwtUtil;
import cn.com.nes.common.util.OpRequestWrap;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;

/**
 * 三方调用日志记录拦截器
 * 只负责第三方调用接口的日志记录
 * 接口不需要token校验，解析token只是为知道是哪个系统的哪个人调用的，方便查询日志。
 */
@Slf4j
@Component
public class GlobalApiLogInterceptor implements HandlerInterceptor {

    // 请求开始时间标识
    private static final String LOGGER_SEND_TIME = "_send_time";

    // 请求日志实体标识
    private static final String LOGGER_ENTITY = "_logger_entity";

    // 请求状态标识
    private static final String LOGGER_STATUS = "_logger_status";

    /**
     * 白名单列表
     */
    private List<String> allowUrls;

    public List<String> getAllowUrls() {
        return allowUrls;
    }

    public void setAllowUrls(List<String> allowUrls) {
        this.allowUrls = allowUrls;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
            Object handler) throws Exception {
        // 记录一个线程追踪标识,最终打印在日志中
        String traceId = StrUtil.isEmpty(request.getHeader("traceId")) ? UUID.randomUUID().toString(): request.getHeader("traceId");
        MDC.put("traceId", traceId);
        String requestUrl = request.getRequestURI();
        if (isAllowUrl(requestUrl)) {
            return true;
        }

        //创建日志实体
        UserOperateLog sysLog = new UserOperateLog();

        //获取请求参数信息
        JSONObject param = getParamters(request);

        // 设置请求参数
        sysLog.setParamData(ObjectUtil.isEmpty(param) ? "{}" : param.toString());
        // 设置IP
        sysLog.setClientIp(IPUtils.getIpAddr(request));
        //设置请求方法,GET,POST...
        sysLog.setMethod(request.getMethod());
        //设置请求路径
        sysLog.setUrl(requestUrl);
        sysLog.setStartTime(new Date());
        //设置请求开始时间
        request.setAttribute(LOGGER_SEND_TIME, System.currentTimeMillis());

        // 获取token信息
        String token = request.getHeader("key");
        if (ObjectUtil.isEmpty(token)) {
            token = request.getHeader("token");
        }
        sysLog.setToken(token);
        if (StrUtil.isNotBlank(token)) {
            try {
                Claims c = JwtUtil.parseWXJWT(token);// 注意：如果jwt已经过期了，这里会抛出jwt过期异常。
                JSONObject token_json = JSONObject.parseObject(c.getSubject());
                String platform = token_json.getString("platform");
                String username = token_json.getString("username");
                String userid = token_json.getString("id");
                sysLog.setPlatform(platform); // 设置调用平台
                sysLog.setUsername(username); // 设置访问者
                sysLog.setUserId(userid); // 设置访问者ID
            } catch (ExpiredJwtException e) {
                log.error("token过期 URL: {}, PARAM: {}, token: {}", sysLog.getUrl(), sysLog.getParamData(), token);
                // throw new SystemException("token 过期！");
            } catch (Exception e) {
                log.error("token 解析失败 URL: {}, PARAM: {}, token: {}", sysLog.getUrl(), sysLog.getParamData(), token);
                // throw new SystemException("token 解析失败");
            }
        }
        //设置请求实体到request内，方便afterCompletion方法调用
        request.setAttribute(LOGGER_ENTITY, sysLog);
        request.setAttribute(LOGGER_STATUS, false);

        log.info("GlobalApiLog ==> PLATFORM: {}, USERID: {}, METHOD: {}, URL: {}, PARAM: {}, IP: {}", sysLog.getPlatform(), sysLog.getUserId(), sysLog.getMethod(), sysLog.getUrl(), sysLog.getParamData(), sysLog.getClientIp());

        return true;
    }

    private static JSONObject getParamters(HttpServletRequest request) throws IOException {
        JSONObject param = new JSONObject();
        if (ObjectUtil.isEmpty(param)) {
            HashMap<String, Object> reqData = new HashMap<String, Object>();
            Enumeration<String> paramNames = request.getParameterNames();
            while (paramNames.hasMoreElements()) {
                String key = paramNames.nextElement().toString();
                String[] value = request.getParameterValues(key);
                if (value.length == 1) {
                    reqData.put(key, value[0]);
                } else {
                    reqData.put(key, value);
                }
            }
            param = JSON.parseObject(JSONObject.toJSONString(reqData));
        }

        if (ObjectUtil.isEmpty(param)) {
            param = JSON.parseObject(JSONObject.toJSONString(request.getParameterMap())
                    .replaceAll("\\[\"", "\"").replaceAll("\"\\]", "\""));
        }

        if (ObjectUtil.isEmpty(param)) {
            try {
                param = JSON.parseObject(((OpRequestWrap) request).getBody());
            } catch (Exception e) {
                // 啥也不干
                // 加上try-catch是因为请求的参数是file时报错：类强转异常 -> OpRequestWrap
            }
        }
        return param;
    }

    private boolean isAllowUrl(String requestUrl) {
        if (CollUtil.isNotEmpty(allowUrls)) {
            for (String url : allowUrls) {
                if (requestUrl.endsWith(url)) {
                    return true;
                }
            }
        }
        return false;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
            ModelAndView modelAndView) throws Exception {
        if (!isAllowUrl(request.getRequestURI())) {
            request.setAttribute(LOGGER_STATUS, true);
            UserOperateLog sysLog = getSysLog(request, response);
            if (ObjectUtil.notEqual(sysLog.getMethod(), "OPTIONS")) {
                log.info("GlobalApiLog ==> PLATFORM: {}, USERID: {}, METHOD: {}, URL: {}, COST: {}", sysLog.getPlatform(), sysLog.getUserId(), sysLog.getMethod(), sysLog.getUrl(), sysLog.getCost());
            }
        }
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
            Exception ex) throws Exception {
        if (!isAllowUrl(request.getRequestURI())) {
            Boolean logger_status = (Boolean) request.getAttribute(LOGGER_STATUS);
            if (!logger_status) {
                UserOperateLog sysLog = getSysLog(request, response);
                if (ObjectUtil.notEqual(sysLog.getMethod(), "OPTIONS")) {
                    log.error("GlobalApiLog ==> PLATFORM: {}, USERID: {}, METHOD: {}, URL: {}, PARAM: {}, TOKEN: {}, IP: {}, COST: {}", sysLog.getPlatform(), sysLog.getUserId(), sysLog.getMethod(), sysLog.getUrl(), sysLog.getParamData(), sysLog.getToken(), sysLog.getClientIp(), sysLog.getCost());
                }
            }
        }
        MDC.clear();
    }

    private static UserOperateLog getSysLog(HttpServletRequest request, HttpServletResponse response) {
        //当前时间
        long currentTime = System.currentTimeMillis();
        //请求开始时间
        long time = Long.valueOf(request.getAttribute(LOGGER_SEND_TIME).toString());
        //获取本次请求日志实体
        UserOperateLog sysLog = (UserOperateLog) request.getAttribute(LOGGER_ENTITY);
        //设置请求结束时间
        sysLog.setEndTime(new Date());
        //设置请求时间差
        sysLog.setCost(Integer.valueOf((currentTime - time) + ""));
        //        userOperateLogMapper.insert(sysLog);
        return sysLog;
    }

}
